<?php

/**
 * Object Cacher
 * @author ReGeDa
 */
abstract class Object_Cacher {

	/**
	 * Cached object
	 * @var array
	 */
	protected $cache = array();

	/**
	 * Extract class name from class path
	 * @param string $classpath
	 */
	private function extract_class_name($classpath) {
		$offset = strrpos($classpath, '/');
		// normalize
		if ($offset)
			$classpath = substr($classpath, $offset + 1);
		// result
		return $classpath;
	}

	/**
	 * Create a new instance
	 * @param string $ns
	 * @param string $classpath
	 * @param string $suffix
	 * @return object
	 */
	protected function create_object($ns, $classpath, $suffix = '') {

		// get class name from namespace
		$clazz = $this->require_class($ns, $classpath);

		if ( ! empty($suffix))
			$clazz .= '_'.$suffix;
		
		// create a new instance
		return new $clazz();
	}

	/**
	 * Require a class
	 * @param string $ns
	 * @param string $classpath
	 * @return string
	 */
	final protected function require_class($ns, $classpath) {

		$filename = PLATCODE_PATH.$ns.'/'.$classpath.'.php';

		if (file_exists($filename)) {
			// require class file
			require_once $filename;
			// get class name from namespace
			return $this->extract_class_name($classpath);
		}
		else
			throw new Platcode_Exception("Unknown '$classpath' Class");
	}
}

/**
 * Object Loader
 * @author ReGeDa
 */
interface Object_Loader {

	/**
	 * Import an object with namespace
	 * @param string $ns
	 * @return object
	 */
	function &import($ns);
}

/**
 * Event Handler Loader
 * @author ReGeDa
 */
class Handler_Loader extends Object_Cacher implements Object_Loader {

	/**
	 * Import event handler
	 * @param string $classpath
	 * @return object
	 */
	public function &import($classpath) {
		if ( ! array_key_exists($classpath, $this->cache))
			$this->cache[$classpath] = $this->create_object('pool', $classpath, 'Handler');

		return $this->cache[$classpath];
	}
}

/**
 * Plugin Loader
 * @author ReGeDa
 */
class Plugin_Loader extends Object_Cacher implements Object_Loader {

	/**
	 * Import plugin
	 * @param string $classpath
	 * @return object
	 */
	public function &import($classpath) {
		if ( ! array_key_exists($classpath, $this->cache)) {

			$obj = $this->create_object('plugins', $classpath);

			if ($obj->is_persistant())
				$this->cache[$classpath] = $obj;
		}
		else
			$obj = $this->cache[$classpath];

		return $obj;
	}

	/**
	 * Include a class
	 * @override
	 * @param string $classpath
	 */
	public function include_class($classpath) {
		return class_exists(
			$this->require_class('plugins', $classpath)
		);
	}
}

/**
 * Data Loade Class
 * @author ReGeDa
 */
abstract class Data_Loader {

	/**
	 * Internal data
	 * @var array
	 */
	protected $data = array();

	/**
	 * Import data into local variable
	 * @param string $ns
	 * @param array $local_data
	 */
	abstract public function import($ns, &$local_data);

	/**
	 * Load internal data
	 * @param string $ns
	 */
	public function load($ns) {
		$this->import($ns, $this->data);
	}

	/**
	 * Fetch data item
	 * @param string $name
	 * @return mixed
	 */
	public function  __get($name) {
		return $this->data[$name];
	}
}

/**
* Configuration Loader
* @author ReGeDa
*/
class Config_Loader extends Data_Loader {

	/**
	 * Import config to external file
	 * @param string $ns
	 * @param array $local_config
	 */
	public function import($ns, &$local_config) {
		include PLATCODE_PATH.'config/'.$ns.'.php';
		$local_config = array_merge($local_config, $config);
	}

	/**
	 * Set configuration item
	 * @param string $name
	 * @param mixed $value
	 */
	public function  __set($name,  $value) {
		$this->data[$name] = $value;
	}
}

/**
 * Language Class
 * @author ReGeDa
 */
class Language extends Data_Loader {

	/**
	 * Language import to external variable
	 * @param string $ns
	 * @param string $local_lang
	 */
	public function import($ns, &$local_lang) {
		include PLATCODE_PATH.'i18n/'.platcode()->config->language.'/'.$ns.'.php';
		$local_lang = array_merge($local_lang, $lang);
	}

}

/**
 * Router
 * @author ReGeDa
 */
class Router {

	/**
	 * Normalize Path information
	 * @param string $path_info
	 * @return string
	 */
	private function normalize_url($path_info) {
		return preg_split('/\//', $path_info, -1, PREG_SPLIT_NO_EMPTY);
	}

	/**
	 * Validate URL parameter
	 * @param array $params
	 * @return string
	 */
	private function validate_param(&$params) {
		// check parameter string
		if (preg_match('/^[a-z]\w*$/', $params[0])) {
			// return parameter and shift it from arguments
			return array_shift($params);
		}
		else
			throw new Platcode_Exception('Denied URL parameter');
	}

	/**
	 * Do prerouting
	 * @param string $path_info
	 * @param array $config
	 * @return boolean
	 */
	private function prerouting($path_info, &$config) {

		if (array_key_exists('route', $config)) {

			// read rules
			foreach($config['route'] as $url => $message) {

				// search pattern
				if (preg_match("#^/$url$#", $path_info, $matches)) {
					// remove first element linked to REGEX rule
					array_shift($matches);
					// send message and exit
					platcode()->trace_message($message[0], $message[1], $matches);
					return TRUE;
				}
			}
		}

		// no routing exists
		return FALSE;
	}

	/**
	 * Constructor
	 */
	public function __construct() {

		$config = array();

		/// load route config
		platcode()->config->import('route', $config);

		if ( ! $this->prerouting($_SERVER['PATH_INFO'], $config)) {

			// input path
			$params = $this->normalize_url($_SERVER['PATH_INFO']);

			// set default
			$handle = $config['default_handle'];
			$event = $config['default_event'];

			// read handler name
			if (count($params)) {

				$handle = $this->validate_param($params);

				// read event string
				if (count($params))
					$event = $this->validate_param($params);

			}

			// send message
			platcode()->trace_message($handle, $event, $params);
		}
	}

}

/**
 * Platcode Exception
 * @author ReGeDa
 */
class Platcode_Exception extends Exception {

	/**
	 * Constructor
	 * @param string $message
	 */
	public function  __construct($message = 'Unexpected Error', $code = 500) {
		parent::__construct($message, $code);
	}

	/**
	 * Show error in browser
	 * @param string $message
	 * @param integer $http_code
	 */
	public function show_error($message = '', $http_code = '') {

		// check HTTP response code
		if (empty($http_code))
			$http_code = $this->getCode();

		// set HTTP code
		platcode()->plugins->import('HTTP')->set_response_code($http_code);

		// output
		echo (empty($message) ? $this->getMessage() : $message);

		// exit
		platcode()->terminate();
	}

	/**
	 * Provide HTTP code output "show_*" handler
	 * @param string $name
	 * @param array $arguments
	 */
	public function  __call($name,  $arguments) {
		if (preg_match('/^show_(\d{3})$/', $name, $matches)) {

			$message = count($arguments) ? $arguments[0] : $this->getMessage();

			$this->show_error($message, $matches[1]);
		}
	}
}

/**
 * Event Manager
 * @author ReGeDa
 */
class Event_Manager {

	/**
	 * Event Manager Instance
	 * @var Event_Manager
	 */
	public static $instance;

	/**
	 * Configuration
	 * @var Config_Loader
	 */
	public $config;

	/**
	 * Plugins
	 * @var Plugin_Loader
	 */
	public $plugins;

	/**
	 * Any middleware variable
	 * @var mixed
	 */
	public $raw = array();

	/**
	 * Language object
	 * @var Language
	 */
	public $lang;

	/**
	 * Dispatcher
	 * @var Platcode_Dispatcher
	 */
	private $dispatcher;

	/**
	 * Constructor
	 */
	public function  __construct() {

		self::$instance = &$this;

		// load functions
		require_once PLATCODE_PATH.'kernel/Functions.php';

		// create needle loaders
		$this->config = new Config_Loader();
		$this->plugins = new Plugin_Loader();
		$this->dispatcher = new Platcode_Dispatcher();
		$this->lang = new Language();

		// load embedded settings
		$this->config->load('platcode');
		$this->lang->load('platcode');

		// Go! Go! Go!
		new Router();
		$this->dispatcher->listen();
	}

	/**
	 * Normalize handle string
	 * @param string $handle
	 * @return string
	 */
	private function normalize_handle($handle) {
		$prefix = NULL;
		$offset = strrpos($handle, '/');
		// normalize
		if ($offset) {
			$prefix = substr($handle, 0, $offset + 1);
			$handle = substr($handle, $offset + 1);
		}
		// result
		return $prefix.ucfirst($handle);
	}

	/**
	 * Trace a message to dispatcher
	 * @param string $handle
	 * @param string $event
	 * @param mixed $args
	 */
	public function trace_message($handle, $event, $args = NULL) {
		$this->dispatcher->accept_message($this->normalize_handle($handle), $event, $args);
	}

	/**
	 * Platcode terminator .. I'll be back!
	 */
	public function terminate() {
		exit;
	}
}

/**
 * Platcode Object
 * @author ReGeDa
 */
class Platcode_Object {

	/**
	 * Constructor
	 */
	public function  __construct() {}

	/**
	 * Send a message to dispatcher
	 * @param string $handle
	 * @param string $event
	 * @param mixed $args 
	 */
	protected function send_message($handle, $event, $args = NULL) {
		platcode()->trace_message($handle, $event, $args);
	}

	/**
	 * Import config in object
	 * @param string $ns
	 * @param object $object
	 * @param array $fields
	 */
	protected function config($ns, $fields = NULL) {

		// local config
		$config = array();
		platcode()->config->import($ns, $config);

		// select fields
		if (empty($fields))
			$fields = array_keys($config);

		// copy configuration
		foreach ($fields as $f)
			$this->$f = $config[$f];
	}
}

/**
 * Plugin Object
 * @author ReGeDa
 */
class Plugin_Object extends Platcode_Object {

	/**
	 * Constructor
	 */
	public function  __construct() {
		parent::__construct();
	}

	/**
	 * Get persistant plugin state
	 * @return boolean
	 */
	public function is_persistant() {
		return FALSE;
	}

}

/**
 * Event Handler
 * @author ReGeDa
 */
class Event_Handler extends Platcode_Object {

	/**
	 * Input variables
	 * @var Input
	 */
	protected $input;

	/**
	 * Output plugin
	 * @var Viewer
	 */
	protected $output;

	/**
	 * Constructor
	 */
	public function  __construct() {
		parent::__construct();
		// Input
		$this->input = &platcode()->plugins->import('Input');
		// Viewer
		$this->output = &platcode()->plugins->import('Viewer');
	}

	/**
	 * Default event listener
	 * @param string $name
	 * @param array $arguments
	 */
	public function  __call($name,  $arguments) {
		if (preg_match('/^on_(\w+)$/', $name, $matches))
			throw new Platcode_Exception("Unknown '$matches[1]' event");
	}
}

/**
 * Database Event Handler
 * @author ReGeDa
 */
class DB_Event_Handler extends Event_Handler {

	/**
	 * Database connection
	 * @var PDO
	 */
	protected $db;

	/**
	 * Constructor
	 */
	public function  __construct() {
		parent::__construct();
		$this->db = &database();
	}

	/**
	 * Normalize error.
	 * Returns:
	 *
	 * * * * * POSTGRESQL
	 * - Constraint name (without suffix "_key") on UNIQUE VIOLATION
	 *
	 * @param PDOException $e
	 * @return string
	 */
	protected function normalize_error(PDOException $e) {

		// select driver name
		switch ($this->db->getAttribute(PDO::ATTR_DRIVER_NAME)) {
			/////////////////// POSTGRESQL
			case 'pgsql':
				// normalize
				switch($e->errorInfo[0]) {
					// UNIQUE VIOLATION
					case 23505:
						preg_match(
							'/duplicate key value violates unique constraint "([a-z\d_]+)_key"/',
							$e->errorInfo[2],
							$matches
						);
						// return constraint name
						return $matches[1];
				}
		}
		return $e->getMessage();
	}
}

/**
 * Dispatcher
 * @author ReGeDa
 */
class Platcode_Dispatcher {

	/**
	 * Handlers
	 * @var Handler_Loader
	 */
	private $handlers;

	private $queue = array();

	/**
	 * Constructor
	 */
	public function  __construct() {
		$this->handlers = new Handler_Loader();
	}

	/**
	 * Accept a message
	 * @param string $handler
	 * @param string $event
	 * @param mixed $args
	 */
	public function accept_message($handler, $event, $args) {
		array_push($this->queue, array($handler, $event, $args));
	}

	/**
	 * Listen a messages
	 */
	public function listen() {
		while($message = array_shift($this->queue)) {

			$h = &$this->handlers->import($message[0]);

			$method = 'on_'.$message[1];

			if ( ! is_array($message[2]))
				$message[2] = array($message[2]);

			// call
			call_user_func_array(array($h, $method), $message[2]);
		}

		// output Platcode variable
		platcode()->plugins->import('Viewer')->response();
	}
}

 // end of file